package org.scalpel.common;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.content.Context;
import android.util.Log;

/**
 * 
 * @author Jerome Monceaux
 *
 * Parse the xml file containing the description of the scene.
 */
public class XMLHandler extends DefaultHandler {

	/**
	 * Complete list of module
	 */
	HashMap<String, Object> fAllModules = new HashMap<String, Object>();

	/**
	 * Object hierarchy path of the currently analysed object
	 */
	ArrayList<HHObject> fCurrentModule = new ArrayList<HHObject>();
	
	/** 
	 * Application context to be shared with module
	 */
	Context fContext;
	
	/**
	 * Constant declared in the xml
	 */
	HashMap<String, Object> fConstant = new HashMap<String, Object>();
	
	
	
	public XMLHandler(Context fContext) {
		super();
		this.fContext = fContext;
	}

	public HashMap<String, Object> getAllObject(){ return fAllModules;}
	
	/** return the path of fCurrentModule */
	private String getPath(){
		String path = "";
		for( HHObject o : fCurrentModule){
			if( !path.equals("") )
				path += ".";
			path += o.fName;
		}
		return path;
	}

	/** Called when tag starts ( ex:- <name>AndroidPeople</name> 
	 * -- <name> )*/
	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {

		if (localName.equalsIgnoreCase("world"))
			parseWorld(uri, localName, qName, attributes);
		else if (localName.equalsIgnoreCase("process"))
			parseProcess(uri, localName, qName, attributes);
		else if (localName.equalsIgnoreCase("constant"))
			parseConstant(uri, localName, qName, attributes);
		else if( localName.equalsIgnoreCase("wire") )
			parseWire(uri, localName, qName, attributes);


	}

	/** Called when tag closing ( ex:- <name>AndroidPeople</name> 
	 * -- </name> )*/
	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {

		if (localName.equalsIgnoreCase("world") ||
				localName.equalsIgnoreCase("process") ){
			fCurrentModule.remove(fCurrentModule.size()-1);
		}

	}

	private void parseConstant(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// TODO : constant has to be scoped in the current module path
		String name = attributes.getValue("name");
		String classname = attributes.getValue("class");
		String value = attributes.getValue("value");
		
		Object val = getObjectFromParamAsString(classname, value);
		fConstant.put(name, val);
	}

	private void parseWire(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		String output = attributes.getValue("output");
		String input = attributes.getValue("input");
		String p = getPath();
		int iPoint = output.lastIndexOf('.');
		String outputName = output.substring(0, iPoint);
		String outputObjName = output.substring(iPoint+1, output.length());
		iPoint = input.lastIndexOf('.');
		String inputName = input.substring(0, iPoint);
		String inputObjName = input.substring(iPoint+1, input.length());
		HHObject o = (HHObject)fAllModules.get(p + "." + outputName); 
		HHObject i = (HHObject)fAllModules.get(p + "." + inputName);
		try{
			i.setWire(o, outputObjName, inputObjName);
		}catch (Exception e) {
			Log.e("XMLHandler", "An error during the wire creation between " + output + " and " + input);
		}
		
	}
	
	@SuppressWarnings({ "rawtypes" })
	private void parseProcess(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		String id = attributes.getValue("id");
		String className = attributes.getValue("class");
		Class theClass = null;
		
		String paramName = attributes.getValue("param");
		String [] paramL = null;
		if( paramName != null){
			paramName = paramName.replace("{","");
			paramName = paramName.replace("}","");
			paramL = paramName.split("[:;]+");
		}
		
		int l = 1;
		if( paramName != null) l = paramL.length/2+1;
		Object[] params = new Object[l];
		Class [] classList = new Class [l];
		try {
			int j = 0;
			classList[j] = Class.forName ("java.lang.String");
			params[j++] = id;
			if( paramName != null){
				for(int i = 0; i < paramL.length; ){
					String type = paramL[i++];
					String value = paramL[i++];
					// TODO : Must explore constant map from this current scope to the root scope. 
					Object c = fConstant.get(value);
					Object p = null;
					if( c != null ){
						p = c;
					}else{
						p = getObjectFromParamAsString(type, value);
					}
					classList[j] = p.getClass();
					params[j++] = p;
				}
			}
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		}

		
		
		try {
			theClass  = Class.forName(className);
			Constructor constructor = ReflectionUtil.getCompatibleConstructor ( theClass, classList );
			Object p = constructor.newInstance (params);
			fCurrentModule.add((HHObject)p);
			fAllModules.put(getPath(), p);

		} catch (ClassNotFoundException e) {
			Log.e("XMLHandler", "error while parsing module : " + id + ". Class has not been found ( " + className + ")." );
			e.printStackTrace();
		} catch (InstantiationException e) {
			Log.e("XMLHandler", "error while parsing module : " + id + ". Could not instantiate class ( " + className + ").");
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			Log.e("XMLHandler", "error while parsing module : " + id + ". Illegal access to class constructor ( " + className + ").");
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			Log.e("XMLHandler", "error while parsing module : " + id + ". Illegal arguement exception form class constructor ( " + className + ").");
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch(Exception e){
			Log.e("XMLHandler", "error while parsing module : " + id + ". Please check constructor parameters.");
			e.printStackTrace();
		}
	}

	private Object getObjectFromParamAsString(String type, String value){
		if( type.equalsIgnoreCase("double") )
			return Double.parseDouble(value);
		else if( type.equalsIgnoreCase("float") )
			return Float.parseFloat(value);
		else if( type.equalsIgnoreCase("boolean") )
			return Boolean.parseBoolean(value);
		else if( type.equalsIgnoreCase("int") )
			return Integer.parseInt(value);
		else if( type.equalsIgnoreCase("short") )
			return Short.parseShort(value);
		else if( type.equalsIgnoreCase("long") )
			return Long.parseLong(value);
		else if( type.equalsIgnoreCase("string") )
			return value;
		else if( type.equalsIgnoreCase("char") )
			return value.charAt(0);
		else if( type.equalsIgnoreCase("byte") )
			return Byte.parseByte(value);
		
		return null;
	}


	private void parseWorld(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		
		String id = attributes.getValue("id");
		if( id == null ) id = "world";
		World w = new World(id);
		fCurrentModule.add( w );
		fAllModules.put(getPath(), w);
		
	}
}
